﻿#include "study04.h"
// C/C++ 学习网站：https://legacy.cplusplus.com 注意旧版本（搜索）和新版本

/*
语句块中，建议尽量少些<=、>=符号，尽量使用< 和 >符号，这样可以避免一些隐式类型转换的性能消耗。

C++ 数组：
	1. 定义数组时，数组的元素个数必须是常量，
		可以用 static 关键字加 const 关键字来保证数组的元素个数是常量 (毫无意义)。
	2.C++ 11 引入了一种更加安全的数组初始化语法，会有一些性能上的开销：
		std::array<类型, 元素个数> 数组名 = {元素1, 元素2, 元素3,..., 元素n};
		然后可以通过 数组名.size() 获取数组的元素个数。这种使用的较少

C++ 字符串：
	字符串的结束标志是 null 字符 '\0'，它们的值都是 0 ；字符串字面量（常量）永远只储存在只读内存中，
	数组存储的字符，不足时不会补 '\0' 所以数组的元素个数和字符串的长度不一定是一致的。
	在 C++ 11 以后标准中，可以通过 string 类来定义字符串，相当于数组的一种。
	string 类提供了对字符串的各种操作，包括拼接、比较、查找等。
		string& string.append(const char* str); 用来添加字符串。
		int string.compare(const string& str); 用来比较字符串。返回 0 表示相等，<0 表示 str 在后，>0 表示 str 在前。
		string& string.erase(size_t pos = 0, size_t len = npos); 用来删除字符串中的一段，pos 表示删除的起始位置 (下标)，len 表示删除的长度，默认为 npos 表示删除到结尾。
		size_t string.size(); 用来获取字符串的长度。
		void string.resize(size_t n[, char c]); 用来改变字符串的长度[，并用 c 填充新位置]。
		string& string.insert(size_t pos, const string& str); 方法可以用来插入字符串。
		size_t string.find(const char* s, size_t pos, size_t n) const; 用来查找子串的位置。pos 表示查找的起始位置 (下标)，n 表示查找的长度。
		string& string.replace(size_t pos, size_t len, const string& str); 用来替换子串，pos 表示替换的起始位置 (下标)，len 表示替换的长度。
		size_t string.copy(char* s, size_t len, size_t pos = 0) const; 用来复制子字符串到 s 字符串数组中，len 表示复制的长度, pos 表示复制的起始位置（下标）。
		const char& string.front() const; 方法可以用来获取字符串的第一个字符。
		const char& string.back() const; 方法可以用来获取字符串的最后一个字符。
		void string.clear(); 方法可以用来清空字符串。
		string string.substr(size_t pos, size_t len = npos); 用来获取子串，pos 表示子串的起始位置 (下标)，len 表示子串的长度，npos 表示子串到结尾。
		bool string.empty() const; 方法可以用来判断字符串是否为空。
		void string.swap(string& x, string& y); 方法可以用来交换两个字符串的内容。
		一些其他方法简单列举如下：
		string.c_str() 方法可以用来获取字符串的 C 风格字符串指针。
		string.data() 方法可以用来获取字符串的 C++ 风格字符串指针。
		string.rfind() 方法可以用来查找子串的最后一次出现的位置。
		string.reserve() 方法可以用来预分配内存，提高字符串的性能。
		string.shrink_to_fit() 方法可以用来释放内存，减少内存碎片，与 reserve() 配合使用。

	C 有个 wchar_t 类型，用来表示宽字符，其实际类型为 unsigned short，对应 utf-8 格式编码；
		wchar_t 可以用来表示中文、日文等。定义在头文件 <vcruntime.h> 里面；
		此外还有 char16_t 和 char32_t 类型，用来表示宽字符，分别需要前缀 u 和 U
		对应 utf-16 和 utf-32 编码；char 后面的的数字表示比特位，这两个类型都不常用。

	C++ 14 标准中，新增了一个命名空间 std::string_literal，
		在这个命名空间中，提供了一些方便的函数（前缀或后缀）来创建字符串字面量：
		s 后缀表示字符串为 string 类型，
		wstring 类表示宽字符串，
		u16string 类表示字符串为 UTF-16 编码，声明字符串时需要前缀 u
		U32string 类表示字符串为 UTF-32 编码，声明字符串时需要前缀 U
		前缀 R 表示字符串为原生字符串，即不包含转义字符（当作普通字符处理），需要括号括起字符串来。

在 C++ 中，运算符本质上是函数，可以重载，可以重载运算符的优先级和结合性。
	运算符重载的语法如下：
		返回类型 operator 运算符(参数列表) { // 函数体 }
		注意：运算符重载的返回类型和参数列表必须与运算符的定义一致。
		运算符重载的优先级和结合性可以参考 C++ 运算符优先级表。
	此处说明：std::cout 类重载了 << 运算符，可以用来输出 C++ 库的各种类型的值。

C++ 一些基本关键字：
	const 关键字用来修饰变量，用来表示该变量的值不能被修改；
		注意在 * 后面加 const 表示指针本身不能被修改，在 & 后面加 const 表示引用对象本身不能被修改；
		在 * 前面加 const 表示指针指向的对象不能被修改，在 & 前面加 const 也表示引用对象不能被修改；
		在 C++ 类中，const 在函数参数之后修饰函数，表示该函数不能修改类的成员变量。
	mutable 关键字用来修饰成员变量，用来表示该变量的值可以被修改；一般和 const 关键字连用；
		注意 mutable 关键字只能用于类或者 lambda 表达式的成员变量，不能用于局部变量。
	this 指针：this 指针指向当前对象本身的地址，可以用来访问类的成员变量和成员函数；
		在类的成员函数中，可以通过 this 指针来访问类的成员变量和成员函数；可以解引用。

C++ 类的成员初始化列表语法：
	类成员初始化列表是 C++ 11 引入的新特性，用来在构造函数中初始化类成员变量。
	在构造函数的初始化列表中，可以指定成员变量的初始值，也可以调用其他构造函数来初始化成员变量。
	初始化列表的语法如下：
		类名::类名(参数列表)
			: 成员变量名1(初始值1), 成员变量名2(初始值2), ... { // 构造函数体 }
			这种语法最好不要在构造函数体中初始化成员列表中已经初始化过的成员变量，避免重复初始化。
		在类里面声明则不需要前面的 类名:: 部分，直接构造函数即可；
		这种语法推荐使用；优点在于如果类成员变量有别的类（必有构造函数），可以避免两次构造，提高效率。

在 C/C++ 中，栈的空间一般比较小（1MB ~ nMB），而堆的空间一般比较大（一般以 GB 为单位）；
	此外，在堆区分配的内存需要手动释放，否则会造成内存泄漏。而且在堆区分配的内存容易产生碎片，
	还有就是堆区分配内存的速度比栈区慢。new 和 delete 关键字用来在堆区分配和释放内存，

C++ 隐式转换说明：
	在 C/C++ 中，有些类型之间的转换是隐式的，整数与浮点数之间的转换不属于隐式转换；
	所以，使用类创建变量赋值其他型时，创建的类会默认找到最匹配的构造方法创建，
	如果有多个构造方法，则会调用最匹配的构造方法；注意：C++ 中只允许一次隐式转换，
	如果使用赋值、传参等操作有多次隐式转换，则会产生编译错误。可以使用 static_cast 关键字明确转换类型；
	explicit 关键字用于申明构造函数不能被隐式调用，只能通过显示的构造函数调用；也就是不会隐式转换类型。

*/

int main()
{
	//C++ 数组：
	{
		print_start("C++ 数组部分测试");
		size_t sz = 10; //数组大小
		IntArray* arr1 = new IntArray(sz); //创建 IntArray 初始化对象
		arr1->init(sz, 0); //初始化数组
		arr1->print_arr(); //打印数组
		//std::cout << "数组元素个数：" << arr1->sz << std::endl; //获取数组元素个数
		arr1->delete_arr(); //销毁数组
		delete arr1; //释放内存，new 和 delete 是一组，相当于 malloc 和 free

		// 定义数组时，数组的元素个数必须是常量，
		// 可以使用 static 关键字加 const 关键字来保证数组的元素个数是常量。
		static const int count = 10;
		//定义数组元素个数，此时 count 是一个常量，注意 vs 中 C 不支持上面这种写法
		int arr2[count]; //定义数组
		int i;
		for (i = 0; i < count; ++i)
		{
			arr2[i] = i + 1; //初始化数组元素
			std::cout << arr2[i] << "  "; //打印数组元素
		}
		std::cout << std::endl;

		// C++ 11 语法测试：
		std::array<float, 10> arr3; //定义数组
		for (i = 0; i < arr3.size(); ++i)
		{
			arr3[i] = i + 1.1f; //初始化数组元素
			std::cout << arr3[i] << "  "; //打印数组元素
		}
		std::cout << std::endl;

		print_end("C++ 数组部分测试");
	}

	// C++ 字符串
	{
		print_start("C++ 字符串部分测试");
		const char* str0 = "测试字符串";//vs 2022 C++14 标准要求常字符串指针变量必须为 const char*
		string str1 = "Hello, world!"; //定义字符串，C++ 11 新标准
		str1[5] = '-'; //修改字符串
		std::cout << "str1 is empty: " << str1.empty() << std::endl; //判断字符串是否为空
		std::cout << str1 << "   size: " << str1.size() << std::endl; //打印字符串
		std::cout << str1.append(" 你好，世界！") << std::endl; //添加字符串
		//string str1 = "Hello, " + " 你好，世界!";// 字符串常量(也叫字面量)不能直接拼接
		// 添加字符串还可以写为：
		//str1 + " 你好，世界!!!";  // ok, += 运算符在 string 类中重载
		str1 += " 我是大傻逼!!!"; // ok
		// 还可以写为：
		string str_add = string("hello, world!") + " 你好，世界!!!";
		std::cout << "str_add is: " << str_add << std::endl; //打印字符串
		std::cout << str1 << std::endl; //添加字符串
		std::cout << str1.insert(6, " wonderful") << std::endl; //插入字符串
		std::cout << str1.replace(7, 9, "beautiful") << std::endl; //替换子串
		std::cout << str1.find("beautiful") << std::endl; //查找子串
		std::cout << str1.substr(7, 9) << std::endl; //获取子串
		std::cout << str1.erase(7, 10) << std::endl; //删除子串
		std::cout << str1.front() << "  " << str1.back() << std::endl; //获取首尾字符
		char str2[10]; //定义字符数组
		str2[(sizeof str2) - 1] = 0; //初始化字符数组
		str1.copy(str2, 5, 7); //拷贝子字符串
		std::cout << str2 << std::endl; //打印子字符串
		string str3 = str1.substr(7); //获取子字符串
		std::cout << str3 << std::endl; //打印子字符串
		std::cout << "str1 compares to str3 is "
			<< str1.compare(str3) << std::endl; //比较字符串
		str1.swap(str3); //交换字符串内容
		std::cout << "str1 is: " << str1 << std::endl;
		std::cout << "str3 is: " << str3 << std::endl; //打印交换后的字符串
		//std::cout << "str1 + str3 is: " << str1 + str3 << std::endl; //拼接字符串

		const wchar_t* wstr0 = L"宽字符测试"; //定义宽字符字符串
		using namespace std::string_literals; // 命名空间声明，C++ 14 标准新增
		std::wstring wstr1 = L"你好，世界！"s + L" 宽字符测试";
		const char* str4 = R"(line1\n\n...
line2\n\n...
	line3\n\n...)"; // 原生字符串，看不见的 \n 、\t 等字符也会被保留
		const char* str5 = "line1...\n"
			"line2...\n"
			"line3..."; //非原生字符串这样写，也可以打印为三行
		std::wcout << L"wstr1 is: " << wstr1 << std::endl; // 使用 wcout 打印宽字符字符串
		std::cout << "str4 is: " << str4 << std::endl; // 使用 wcout 打印原生字符串
		std::cout << "str4 is: " << str5 << std::endl; // 使用 wcout 打印原生字符串

		print_end("C++ 字符串部分测试");
	}

	// C++ 运算符重载与关键字
	{
		print_start("C++ 运算符重载与关键字部分测试");
		test_overload();
		std::cout << "C++ 运算符重载部分测试完成" << std::endl << std::endl;

		test_key_word();
		print_end("C++ 运算符重载与关键字部分测试");
	}

	system("pause");
	return 0;
}